#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
This Module is for defining the sequenses of a commands to plot the fracture 
diagramm. The Module reads a list of a fracture data and returns  the sequenses 
of a commands. The fracture data is a list of the numbers and chars.
Commands are independent from the method of the plotting and from 
the coordinate system. And it is easy to addapt any other part of the programm 
to work with this.
There are several commands:
Point (X1, Y1), size, color
Line (X1, Y1, X2, Y2), width, color
Upper left corner of t he canvashas the cordinate (-1,-1). Bottom Right corner 
has the coordinates (1,1). the center of the canvas has the coordinates (0,0).
Each line in list must has at least two numbers: Strike and Dip. 
For example: [125, 15]
Such numbers definde the attitude of a plane. If you want to defind the attitude 
of a line in a plane there must be a third numbers for trend, Pitch or Plunge.
If theris no characters just next to the digits then the Modul will expect that 
this numbers are define the Trend. But it is possible to define dirrectly what's
the type of the attitude of the line.
TR is for trend
PL is for plunge
PT is for pitch.
For example: [125, 15, 32, 'PL']

The next several characters are for defining the looks of 
a plot.
p - point in dipiing of the plane. DEFULT
l - the line of a great circle of the dipping of the plane
z - the line between the dip of the plane and the center of the plot
d - the small piece of the line shows the orientation of the line in the plane
v - the vectors shows the dirrection of the possible movement in the plane
n - the normal to the plane
x - the line between the normal to the plane and the center of the plot
h - the same as the v but from the normal
g - the same as the d but from the normal
w - the same as v but along the curve of a great circle
q - the same as d but along the curve of a great circle
r - the same as h but along the curve of a great circle
t - the same as g but along the curve of a great circle

You can use one char or you can use combination of a chars to specify the looks.
If no chars will be found the 'p' chars will be expected, and if there is attitude 
of a line in the data the 'v' char will be expected

NB: the d, v,h and g letters are all plots the vectors oriented against the nort 
directions. So it's better to use wqrt wich are more correct.


the next word  is for definding the color.

The complete example:
[125, 15, 32, 'PL', 'alv', 'red']

"""
from plane import Plane
from math import sin, cos, radians, atan2



def ReturnCommands(data, isnormals = False, isnormalshard=False, allTR=False, hemisphere='lower', isstress=False):
    Commands = []
    plot = plot_func()
    plot.isnormal = isnormals
    if isstress:
        for plane_att in data:
            Commands.append(plot.p(plane_att, hemisphere))
        return Commands

    for plane_att in data:               
        if isnormals: 
            if not plane_att[4]:
                plane_att[4]='n'
        if isnormalshard:
            if not plane_att[4]:
                plane_att[4]='n'
            plane_att[4] = plane_att[4].replace('p', 'n')
            plane_att[4] = plane_att[4].replace('l', 'n')
            plane_att[4] = plane_att[4].replace('d', 'g')
            plane_att[4] = plane_att[4].replace('v', 'h')
            plane_att[4] = plane_att[4].replace('w', 'r')
            plane_att[4] = plane_att[4].replace('q', 't')
        if not plane_att[4]: plane_att[4] = 'p'
        if plane_att[2]:
            if plane_att[4].find('v') == -1 and \
               plane_att[4].find('w') == -1 and \
               plane_att[4].find('h') == -1 and \
               plane_att[4].find('r') == -1:
                if isnormals or isnormalshard:
                    plane_att[4]+='r'
                else:
                    plane_att[4]+='w'
        if allTR and plane_att[2]:
            plane_att[4] = plane_att[4].replace('d', 'v')
            plane_att[4] = plane_att[4].replace('q', 'w')
            plane_att[4] = plane_att[4].replace('t', 'r')
            plane_att[4] = plane_att[4].replace('g', 'h')
        for f in plot.func_list:            
            if plane_att[4].find(f)>-1:
                func = getattr(plot, f)                
                Commands.append(func(plane_att, hemisphere))
    return Commands
        


class  plot_func():
    '''
    Class for plotting different types of data on the stereographic diagrams.
    Each metod servies for plottin its own type of data (great circle,normal 
    to a plane, etc).
    '''
    func_list = ['l','p','n','z','x','v','h','d','g','t','q','w','t','r']
    isnormal = False
    
    def p(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        cmds.append(['point', 
                     self.get_coords(a, hemisphere), 
                     p_att[6]])
        return cmds

    def n(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        cmds.append(['point', 
                     self.get_coords(a.normal(), hemisphere), 
                     p_att[6]])
        return cmds

    def l(self, p_att, hemisphere):        
        a = Plane(p_att[0], p_att[1])
        cmds = []
        if p_att[1]==90:
            x1,y1=self.get_coords(a.rotated(90), hemisphere)
            x2,y2=self.get_coords(a.rotated(-90), hemisphere)
            cmds.append(['line', 
                        [x1,y1,x2,y2], 
                        p_att[6]])
            return cmds
        points = []
        for angle in range(-89, 89, 5):
            x,y = self.get_coords(a.rotated(angle), hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append(['line', 
                      [points[i][0],points[i][1],points[i+1][0],points[i+1][1]], 
                         p_att[6]])
        return cmds
        
    def z(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        x,y=self.get_coords(a, hemisphere)
        cmds = []
        cmds.append(['line',
                     [x,y, 0, 0], 
                     p_att[6]])
        return cmds
        
        
    def x(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        x,y=self.get_coords(a.normal(), hemisphere)
        cmds.append(['line',
                     [x,y, 0, 0], 
                     p_att[6]])
        return cmds

    def v(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        
        angle = self.get_angle(p_att)
        r = 0.1
        rv = 0.04 
        x, y = self.get_coords(a, hemisphere)
        x1, y1 = x+sin(angle)*r, y-cos(angle)*r   
        xv1, yv1 = x1-sin(angle+radians(10))*rv, y1+cos(angle+radians(10))*rv
        xv2, yv2 = x1-sin(angle-radians(10))*rv, y1+cos(angle-radians(10))*rv
        
        cmds.append(['line',
                     [x,y, x1,y1], 
                     p_att[6]])
        cmds.append(['line',
                     [x1,y1, xv1,yv1], 
                     p_att[6]])
        cmds.append(['line',
                     [x1,y1, xv2,yv2], 
                     p_att[6]])            
        return cmds

    def h(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        
        angle = self.get_angle(p_att)
        r = 0.1
        rv = 0.04
        x, y = self.get_coords(a.normal(), hemisphere)
        x1, y1 = x+sin(angle)*r, y-cos(angle)*r   
        xv1, yv1 = x1-sin(angle+radians(10))*rv, y1+cos(angle+radians(10))*rv
        xv2, yv2 = x1-sin(angle-radians(10))*rv, y1+cos(angle-radians(10))*rv
        
        cmds.append(['line',
                     [x,y, x1,y1], 
                     p_att[6]])
        cmds.append(['line',
                     [x1,y1, xv1,yv1], 
                     p_att[6]])
        cmds.append(['line',
                     [x1,y1, xv2,yv2], 
                     p_att[6]]) 
        return cmds
        
    def d(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        
        angle = self.get_angle(p_att)
        r = 0.1
        x, y = self.get_coords(a, hemisphere)
        x0, y0 = x+sin(angle)*r, y-cos(angle)*r   
        x1, y1 = x-sin(angle)*r, y+cos(angle)*r   
        
        cmds.append(['line',
                     [x0,y0, x1,y1], 
                     p_att[6]])
        return cmds    

    def g(self, p_att, hemisphere):
        a = Plane(p_att[0], p_att[1])
        cmds = []
        
        angle = self.get_angle(p_att)
        r = 0.1
        x, y = self.get_coords(a.normal(), hemisphere)
        x0, y0 = x+sin(angle)*r, y-cos(angle)*r   
        x1, y1 = x-sin(angle)*r, y+cos(angle)*r   
        
        cmds.append(['line',
                     [x0,y0, x1,y1], 
                     p_att[6]])
        return cmds    

    def q(self, p_att, hemisphere):
        if hemisphere == "upper": p_att[2] = (p_att[2]+180)%360
        a = Plane(p_att[0], p_att[1])
        if p_att[2]==0:p_att[2]=.0001
        if p_att[2]==180:p_att[2]=180.0001
        direction = p_att[2]
        if (a>90 and a<180) or (a<360 and a>270):
            direction = (direction+180)%360
        b = Plane(direction, .0001)
        cmds = []
        
        al = 14 # length of an arrow in degrees
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        points=[]
        for angle in range (ca, ca + al, 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        b = Plane(p_att[2]+180, 0)
        a = Plane(p_att[0], p_att[1])
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        points=[]
        for angle in range (ca, ca + al, 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        return cmds
    
    def w(self, p_att, hemisphere):
        if p_att[2]==p_att[0]: p_att[2]=(p_att[2]+1)%360 # strange bug i fixed with dirty trick
        if p_att[2]==(p_att[0]+180)%360: p_att[2]=(p_att[2]+1)%360 # strange bug i fixed with dirty trick
        if hemisphere == "upper": p_att[2] = (p_att[2]+180)%360
        a = Plane(p_att[0], p_att[1])
        if p_att[2]==0:p_att[2]=.0001
        if p_att[2]==180:p_att[2]=180.0001
        direction = p_att[2]
        if (a>90 and a<180) or (a<360 and a>270):
            direction = (direction+180)%360
        if (direction - a.dir)%180 < 90: direction = (direction + 180) % 360
        b = Plane(direction, .0001)
        cmds = []
        
        al = 14 # length of an arrow in degrees
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        # draw lines
        points=[]
        for angle in range (int(ca), int(ca + al), 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        # draw triangle
        x, y = points[-2]
        x1, y1 = points[-1]
        angl= atan2((y1-y),(x1-x)) + (radians(90))
        rv = .04 # length of a triangle lines
        xv1, yv1 = x1-sin(angl+radians(10))*rv, y1+cos(angl+radians(10))*rv
        xv2, yv2 = x1-sin(angl-radians(10))*rv, y1+cos(angl-radians(10))*rv
        #line xv1,yv1 - x1,y1
        cmds.append([   'line', 
                        [ xv1, yv1, 
                          x1, y1 ],
                        p_att[6]])
        #line xv2,yv2 - x1,y1
        cmds.append([   'line', 
                        [ xv2, yv2, 
                          x1, y1 ],
                        p_att[6]])
        
        return cmds
            
    def t(self, p_att, hemisphere):
        if hemisphere == "upper": p_att[2] = (p_att[2]+180)%360
        a = Plane(p_att[0], p_att[1]).normal()
        if p_att[2]==0:p_att[2]=.0001
        if p_att[2]==180:p_att[2]=180.0001
        direction = p_att[2]
        if (a>90 and a<180) or (a<360 and a>270):
            direction = (direction+180)%360
        b = Plane(direction, .0001)
        cmds = []
        
        al = 14 # length of an arrow in degrees
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        points=[]
        for angle in range (ca, ca + al, 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        b = Plane(p_att[2]+180, 0)
        a = Plane(p_att[0], p_att[1]).normal()
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        points=[]
        for angle in range (ca, ca + al, 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        return cmds

    def r(self, p_att, hemisphere):
        if p_att[2]==p_att[0]: p_att[2]=(p_att[2]+1)%360 # strange bug that i fix with a dirty trick
        if p_att[2]==(p_att[0]+180)%360: p_att[2]=(p_att[2]+1)%360 # strange bug i fixed with dirty trick
        if hemisphere == "upper": p_att[2] = (p_att[2]+180)%360
        a = Plane(p_att[0], p_att[1]).normal()
        if p_att[2]==0:p_att[2]=.0001
        if p_att[2]==180:p_att[2]=180.0001
        direction = p_att[2]
        if (a>90 and a<180) or (a<360 and a>270):
            direction = (direction+180)%360
        if (direction - a.dir)%180 < 90: direction = (direction + 180) % 360
        b = Plane(direction, .0001)
        cmds = []
        
        al = 14 # length of an arrow in degrees
        c = a.get_perpendicular_between(b).normal()
        ca = c.return_angle_between(a)
        ang_sub = c.dir - a.dir
        # draw lines
        points=[]
        for angle in range (int(ca), int(ca + al), 5):
            if (ang_sub>0 and ang_sub<180) or \
               (ang_sub<-180 and ang_sub>-360):
                a = c.rotated(-1*angle)
            else:
                a = c.rotated(angle)
            if a.dip <0: a = Plane(a.dir+180, a.dip*-1)
            x,y = self.get_coords(a, hemisphere)
            points.append([x,y])
        for i in range(len(points)-1):
            cmds.append([   'line', 
                            [ points[i][0], points[i][1], 
                              points[i+1][0], points[i+1][1] ],
                            p_att[6]])
        # draw triangle
        x, y = points[-2]
        x1, y1 = points[-1]
        angl= atan2((y1-y),(x1-x)) + (radians(90))
        rv = .04 # length of a triangle lines
        xv1, yv1 = x1-sin(angl+radians(10))*rv, y1+cos(angl+radians(10))*rv
        xv2, yv2 = x1-sin(angl-radians(10))*rv, y1+cos(angl-radians(10))*rv
        #line xv1,yv1 - x1,y1
        cmds.append([   'line', 
                        [ xv1, yv1, 
                          x1, y1 ],
                        p_att[6]])
        #line xv2,yv2 - x1,y1
        cmds.append([   'line', 
                        [ xv2, yv2, 
                          x1, y1 ],
                        p_att[6]])
        
        return cmds
        
    def get_coords(self, pln, hemisphere):
        if (hemisphere == 'lower' and pln.cos2 < 0) or \
                                (hemisphere == 'upper' and pln.cos2 > 0) :
            return pln.cos3,  (-1) * pln.cos1
        else:
            return (-1) * pln.cos3, pln.cos1

    def get_angle(self, p_att):
        if p_att[3] == 'PL':
            return None
        if p_att[3] == 'PT':
            return None
        else:
            return radians(p_att[2])
